﻿using System;
using System.Net.NetworkInformation;
using System.Web;

namespace Altairis.IisNetDiag {
    public class TraceRouteHandler : IHttpHandler {
        private const int DATA_LENGTH = 32;

        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/plain";
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

            // Get parameters
            var target = context.Request.QueryString.GetValue("target", context.Request.UserHostAddress);
            var timeout = context.Request.QueryString.GetValue("timeout", 500, 10, 1000);
            var maxTtl = context.Request.QueryString.GetValue("maxttl", 40, 1, 100);
            var count = context.Request.QueryString.GetValue("count", 3, 0, 10);
            var resolve = context.Request.QueryString.GetValue("resolve", false);

            // Check hostname
            if (!target.IsValidHost()) {
                context.Response.Output.WriteLine("Invalid target (must be host name or IP address)");
                return;
            }

            // Display header
            context.Response.Output.WriteLine("Tracing route to {0} over maximum {1} hosts, timeout {2} ms:",
                target,     // 0
                maxTtl,     // 1
                timeout);   // 2
            context.Response.Output.WriteLine();

            // Prepare ping data
            var data = new byte[DATA_LENGTH];
            var r = new Random();
            r.NextBytes(data);

            // Trace
            try {
                using (var ping = new Ping()) {
                    int currentTtl = 0;
                    while (currentTtl < maxTtl) {
                        context.Response.Flush();
                        currentTtl++;

                        var reply = ping.Send(target, timeout, data, new PingOptions { Ttl = currentTtl });
                        if (reply.Status == IPStatus.Success || reply.Status == IPStatus.TtlExpired) {
                            // Found some host
                            context.Response.Output.Write("{0,3}: {1,-16}",
                                currentTtl,             // 0
                                reply.Address);         // 1

                            // Get reply times
                            for (int i = 0; i < count; i++) {
                                var statReply = ping.Send(reply.Address, timeout, data, new PingOptions { Ttl = currentTtl });
                                if (statReply.Status == IPStatus.Success) {
                                    context.Response.Output.Write("{0,5} ms", statReply.RoundtripTime);
                                }
                                else {
                                    context.Response.Output.Write("    *   ");
                                }

                            }
                            context.Response.Output.WriteLine();

                            // Resolve host name
                            if (resolve) {
                                try {
                                    var dns = System.Net.Dns.GetHostEntry(reply.Address);
                                    if (!reply.Address.ToString().Equals(dns.HostName)) context.Response.Output.WriteLine("     [{0}]", dns.HostName);
                                }
                                catch { } // ignore resolving errors
                            }

                            if (reply.Status == IPStatus.Success) {
                                // Reached target
                                context.Response.Output.WriteLine();
                                context.Response.Output.WriteLine("Trace completed.");
                                currentTtl = -1;
                                break;
                            }
                        }
                        else {
                            // Some error
                            context.Response.Output.WriteLine("{0,3}: {1}",
                                currentTtl,             // 0
                                reply.Status);          // 1
                        }
                    }
                    if (currentTtl != -1) {
                        context.Response.Output.WriteLine();
                        context.Response.Output.WriteLine("Unable to complete trace.");
                    }
                }
            }
            catch (Exception ex) {
                context.Response.Output.WriteLine();
                context.Response.Output.WriteLine("Errror: {0}", ex.Message);
            }

        }

        public bool IsReusable {
            get {
                return true;
            }
        }
 
    }
}
